/*
 * ImportData.java
 */
package com.vs.kindlecoll.IO;

import com.vs.kindlecoll.Beans.Book;
import com.vs.kindlecoll.Beans.BooksCollection;
import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.swing.filechooser.FileSystemView;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 *
 * @author volodymyr.sadovyy
 */
public class KindleInteractor {

    static File kindle;
    static File documentsDir;
    static File systemDir;
    static LinkedList<Book> books = new LinkedList<Book>();
    static LinkedList<BooksCollection> colls = new LinkedList<BooksCollection>();
    private static File collection_json;

    //Just check for drive name initially; USB libs for Java seems to be abandoned.
    //Additionally:
    //TODO: Check on Linux \ Mac OS
    //TODO: Add UI component for chusing drive in case more than one devise attached.
    static public void initKindleDrive() throws Exception {
        List<File> files = Arrays.asList(File.listRoots());

        for (File f : files) {
            //*nix detection
            if (f.getAbsolutePath().equals("/")) {
                ProcessBuilder pb = new ProcessBuilder("mount");
                Process p = pb.start();
                BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));

                String res;
                while (null != (res = br.readLine())) {
                    String[] resParts = res.split(" ");
                    for (String s : resParts) {
                        if (s.toLowerCase().contains(IOConfig.KINDLE_DRIVE_NAME)) {
                            kindle = new File(s);
                        }
                    }
                }
            }

            //Windows detection
            String mountPointName = FileSystemView.getFileSystemView().getSystemDisplayName(f);
            String mountPointDesc = FileSystemView.getFileSystemView().getSystemTypeDescription(f);
            if (mountPointName.toLowerCase().contains(IOConfig.KINDLE_DRIVE_NAME)
                    || null != mountPointDesc && mountPointDesc.toLowerCase().contains(IOConfig.KINDLE_DRIVE_NAME)) {
                kindle = f;
            }
            if (null != kindle) {
                File[] kindleFoldersArray = kindle.listFiles(new DocDirFilter());

                //In case we have more than one "documents" folder on the Kindle drive
                if (kindleFoldersArray.length != 2) {
                    throw new Exception("'system' and 'documents' forlders are missed.");
                }

                if (kindleFoldersArray[0].getName().startsWith(
                        IOConfig.DOCUMENTS_DIR_NAME)) {
                    documentsDir = kindleFoldersArray[0];
                    systemDir = kindleFoldersArray[1];
                } else if (kindleFoldersArray[0].getName().startsWith(
                        IOConfig.SYSTEM_DIR_NAME)) {
                    systemDir = kindleFoldersArray[0];
                    documentsDir = kindleFoldersArray[1];
                } else {
                    throw new Exception("'system' and 'documents' forlders are missed.");
                }

                //init books
                getAllBooksFromFilesystem(documentsDir);
                //init colls
                parseCollectionJSON();

                return;
            }
        }
        throw new Exception("Kindle drive does not detected.");
    }

    static public LinkedList<Book> getBooks() throws Exception {
        if (null == books || books.isEmpty()) {

            if (null == kindle) {
                initKindleDrive();
            }

        }

        Collections.sort(books);

        return books;
    }

    private static void getAllBooksFromFilesystem(File docFolder) throws IOException {
        for (File f : Arrays.asList(docFolder.listFiles(new BooksFilesFilter()))) {
            if (f.isFile()) {
                books.add(new Book(f));
            } else {
                getAllBooksFromFilesystem(f);
            }
        }
    }

    static private void parseCollectionJSON() throws Exception {
        if (null == colls || colls.isEmpty()) {
            JSONObject collJSON = getCollJSON();
            Iterator i = collJSON.keys();

            while (i.hasNext()) {
                String key = i.next().toString();

                JSONObject collElementsJSON = collJSON.getJSONObject(key);
                JSONArray booksArrayJSON = collElementsJSON.getJSONArray("items");
                List<Book> booksHashes = new ArrayList<Book>(booksArrayJSON.length());

                for (int j = 0; j < booksArrayJSON.length(); ++j) {
                    //TODO: add Hash parsing
                    Book b = new Book();
                    b.setKindleHash(booksArrayJSON.getString(j));
                    booksHashes.add(b);
                }

                colls.add(new BooksCollection(key, booksHashes));
            }
        }

    }

    static public List<BooksCollection> getCollections() throws Exception {
        if (null == colls) {
            parseCollectionJSON();
        }
        return colls;
    }

    static public void saveCollections() throws Exception {
        JSONObject collsJSON = new JSONObject();
        for (BooksCollection c : colls) {
            JSONObject cJSON = new JSONObject();
            cJSON.put("items", new JSONArray(c.getBookHashes()));
            cJSON.put("lastAccess", Long.toString(System.currentTimeMillis()));

            collsJSON.put(c.getName(), cJSON);
        }


        String collFileContent = collsJSON.toString(4);

        File bakKindle = new File(collection_json.getAbsolutePath() + ".bak");
        String kindleFilePath = collection_json.getAbsolutePath();

        if (bakKindle.exists()) {
            bakKindle.delete();
        }
        collection_json.renameTo(bakKindle);

        File newKindle = new File(kindleFilePath);

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newKindle), "UTF-8"));

        bw.write(collFileContent);
        bw.close();
    }

    /*
     * Return mount point for Kindle
     */
    static public File getKindleFileDrive() throws Exception {
        if (null == kindle) {
            initKindleDrive();
        }
        return kindle;
    }

    /*
     * Initialize internal JSON object with actual collections.json data
     */
    static private JSONObject getCollJSON() throws Exception {
        if (null == documentsDir) {
            initKindleDrive();
        }

        for (File f : systemDir.listFiles()) {
            if (f.getName().startsWith("collections")
                    && f.getName().endsWith(".json")) {

                collection_json = f;

                FileInputStream fis = new FileInputStream(f);
                InputStreamReader isr = new InputStreamReader(fis, IOConfig.JSON_ENCODING);

                Reader reader = new BufferedReader(isr);

                char[] contents = new char[1024];
                StringBuilder collectionFileContent = new StringBuilder();
                while (reader.read(contents) != -1) {
                    collectionFileContent.append(new String(contents));
                }

                JSONObject collections;
                if (null != collectionFileContent && collectionFileContent.length() > 0) {
                    collections = new JSONObject(collectionFileContent.toString());
                } else {
                    collections = new JSONObject();
                }

                reader.close();
                isr.close();
                fis.close();

                return collections;


            }
        }

        throw new Exception("collection.json does not found on the Kindle drive");

    }

    static public void delListBook(List<Book> booksToDelete) {
        for (Book b : booksToDelete) {
            books.remove(b);
        }
    }

    static public void delCollsBook(Book b, BooksCollection c) throws Exception {
        if (null == c || null == b) {
            return;
        }
        if (!b.getTitle().equalsIgnoreCase(BooksCollection.EMPTY_COLL_MESS) && !c.removeBook(b)) {
            throw new Exception("Cannot delete book " + b + " in collection " + c);
        }
    }

    static public void delCollection(BooksCollection c) {
        colls.remove(c);
    }

    static public void createCollection() {
        colls.add(new BooksCollection("<New Collection>"));
    }

    public static void addBookToCollection(BooksCollection c, Book b) {
        for (BooksCollection i : colls) {
            if (i.equals(c)) {
                i.addBook(b);
            }
        }
    }

    static class DocDirFilter implements FileFilter {

        public boolean accept(File pathname) {
            boolean fileOK = true;

            fileOK &= (pathname.getName().startsWith(IOConfig.DOCUMENTS_DIR_NAME)
                    || (pathname.getName().startsWith(IOConfig.SYSTEM_DIR_NAME)));

            fileOK &= pathname.isDirectory();

            return fileOK;

        }
    }
    
    static class BooksFilesFilter implements FileFilter {

        public boolean accept(File pathname) {
            boolean fileOK = true;

            fileOK &= (pathname.isFile() && (pathname.getName().endsWith(IOConfig.MOBI_FILE_EXT)
                    || (pathname.getName().endsWith(IOConfig.AZW_FILE_EXT)))) 
                    || pathname.isDirectory();

            return fileOK;

        }
    }

}

